package oglutil

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/go-gl/gl/v4.6-core/gl"
)

//Shader 管理openglshader
type Shader struct {
	ID           uint32
	vertexPath   string
	fragmentPath string
}

//NewShader 返回1个shader
func NewShader(vertexPath, fragmentPath string) *Shader {
	return &Shader{
		vertexPath:   vertexPath,
		fragmentPath: fragmentPath,
	}
}

//Compile 编译shader
func (sd *Shader) Compile() error {
	vertex, err := sd.compile(sd.vertexPath, gl.VERTEX_SHADER)
	if err != nil {
		return err
	}
	fragment, err := sd.compile(sd.fragmentPath, gl.FRAGMENT_SHADER)
	if err != nil {
		return err
	}
	id := gl.CreateProgram()
	gl.AttachShader(id, vertex)
	gl.AttachShader(id, fragment)
	gl.LinkProgram(id)
	gl.DeleteShader(vertex)
	gl.DeleteShader(fragment)
	var status int32
	gl.GetProgramiv(id, gl.LINK_STATUS, &status)
	if status == gl.FALSE {
		var logLength int32
		gl.GetProgramiv(id, gl.INFO_LOG_LENGTH, &logLength)

		log := strings.Repeat("\x00", int(logLength+1))
		gl.GetProgramInfoLog(id, logLength, nil, gl.Str(log))

		return fmt.Errorf("failed to link program: %v", log)
	}
	sd.ID = id
	return nil
}

//Use 使用指定的shader
func (sd *Shader) Use() {
	gl.UseProgram(sd.ID)
}

//SetBool 设置bool
func (sd *Shader) SetBool(name string, value bool) {
	var iv int32
	if value {
		iv = 1
	}
	gl.Uniform1i(gl.GetUniformLocation(sd.ID, gl.Str(name+"\x00")), iv)
}

//SetInt 设置int32
func (sd *Shader) SetInt(name string, value int32) {
	gl.Uniform1i(gl.GetUniformLocation(sd.ID, gl.Str(name+"\x00")), value)
}

//SetFloat 设置float32
func (sd *Shader) SetFloat(name string, value float32) {
	gl.Uniform1f(gl.GetUniformLocation(sd.ID, gl.Str(name+"\x00")), value)
}

//SetM4 设置M4的矩阵
func (sd *Shader) SetM4(name string, count int32, m4 *float32) {
	gl.UniformMatrix4fv(gl.GetUniformLocation(sd.ID, gl.Str(name+"\x00")), count, false, m4)
}

//gl.VERTEX_SHADER
func (sd *Shader) compile(path string, shaderType uint32) (uint32, error) {
	f, err := os.Open(path)
	if err != nil {
		return 0, err
	}
	defer f.Close()
	content, err := ioutil.ReadAll(f)
	if err != nil {
		return 0, err
	}
	cshader := string(content) + "\x00"
	shader := gl.CreateShader(shaderType)
	cstr, free := gl.Strs(cshader)
	gl.ShaderSource(shader, 1, cstr, nil)
	free()
	gl.CompileShader(shader)

	var status int32
	gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
	if status == gl.FALSE {
		var logLength int32
		gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)

		log := strings.Repeat("\x00", int(logLength+1))
		gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
		gl.DeleteShader(shader)
		return 0, errors.New(log)
	}
	return shader, nil
}
